/*
 * ioGame
 * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.
 * # iohao.com . 渔民小镇
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.iohao.example.sdk;

import com.iohao.example.sdk.logic.SdkLogicServer;
import com.iohao.example.sdk.logic.action.SdkCmd;
import com.iohao.game.action.skeleton.core.DataCodecKit;
import com.iohao.game.action.skeleton.i18n.Bundle;
import com.iohao.game.action.skeleton.i18n.MessageKey;
import com.iohao.game.action.skeleton.protocol.BarMessage;
import com.iohao.game.action.skeleton.protocol.wrapper.LongValue;
import com.iohao.game.bolt.broker.client.AbstractBrokerClientStartup;
import com.iohao.game.common.kit.concurrent.TaskKit;
import com.iohao.game.common.kit.time.CacheTimeKit;
import com.iohao.game.external.core.ExternalServer;
import com.iohao.game.external.core.aware.UserSessionsAware;
import com.iohao.game.external.core.config.ExternalGlobalConfig;
import com.iohao.game.external.core.hook.UserHook;
import com.iohao.game.external.core.hook.internal.IdleProcessSetting;
import com.iohao.game.external.core.netty.DefaultExternalServer;
import com.iohao.game.external.core.netty.hook.DefaultSocketIdleHook;
import com.iohao.game.external.core.netty.hook.SocketIdleHook;
import com.iohao.game.external.core.netty.simple.NettyRunOne;
import com.iohao.game.external.core.session.UserSession;
import com.iohao.game.external.core.session.UserSessions;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

/**
 * @author 渔民小镇
 * @date 2024-11-01
 */
@Slf4j
public final class SdkApplication {
    public static void main(String[] args) {
        // i18n: CHINA or US
        Locale.setDefault(Locale.CHINA);
        new NettyRunOne()
                // GameExternalServer. 游戏对外服
                .setExternalServer(createExternalServer())
                // GameLogicServerList. 游戏逻辑服列表
                .setLogicServerList(listLogic())
                .startup();
    }

    static List<AbstractBrokerClientStartup> listLogic() {
        return List.of(
                new SdkLogicServer()
        );
    }

    private static ExternalServer createExternalServer() {
        int externalPort = ExternalGlobalConfig.externalPort;
        extractedAccess();

        // Build GameExternalServer https://iohao.github.io/game/docs/overall/external_intro
        var builder = DefaultExternalServer.newBuilder(externalPort);
        var setting = builder.setting();

        /*
         * https://iohao.github.io/game/docs/external/idle
         * About netty heartbeat. See SocketIdleHandler
         *
         * cn: 创建 IdleProcessSetting，用于心跳相关的设置
         */
        var idleProcessSetting = new IdleProcessSetting()
                .setIdleTime(10)
                .setIdleHook(new MySocketIdleHook());
        setting.setIdleProcessSetting(idleProcessSetting);

        setting.setUserHook(new MyUserHook());

        return builder.build();
    }

    static void extractedAccess() {
        // https://iohao.github.io/game/docs/external/access_authentication

        var accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;
        // true: Users must loginVerify before they can access the business methods
        accessAuthenticationHook.setVerifyIdentity(true);
        // Ignore cmd: These ignored routes can be accessed without the need to loginVerify
        accessAuthenticationHook.addIgnoreAuthCmd(SdkCmd.cmd, SdkCmd.loginVerify);
        accessAuthenticationHook.addIgnoreAuthCmd(SdkCmd.cmd);

        /*
         * Notice:
         *     Reject cmd: Routes that deny access to any player.
         *     These action methods will not be generated by the SDK.
         *
         *     sdk 将不会生成这些配置了 Rejection 的 action 方法。
         */
        accessAuthenticationHook.addRejectionCmd(SdkCmd.cmd, SdkCmd.internalAddMoney);
    }

    private static class MySocketIdleHook implements SocketIdleHook {
        final DefaultSocketIdleHook defaultSocketIdleHook = new DefaultSocketIdleHook();
        /** currentTimeMillis */
        volatile byte[] timeBytes;

        MySocketIdleHook() {
            updateTime();
            // 每秒更新当前时间
            TaskKit.runInterval(this::updateTime, 1, TimeUnit.SECONDS);
        }

        private void updateTime() {
            var data = LongValue.of(CacheTimeKit.currentTimeMillis());
            timeBytes = DataCodecKit.encode(data);
        }

        @Override
        public boolean callback(UserSession userSession, IdleStateEvent event) {
            return defaultSocketIdleHook.callback(userSession, event);
        }

        @Override
        public void pongBefore(BarMessage idleMessage) {
            /*
             * Set the time of the current server so that the time of the client and the server can be synchronized.
             * cn: 设置当前服务器的时间，以便客户端与服务器的时间同步。
             */
            idleMessage.setData(timeBytes);
            log.info("response Idle");
        }
    }

    private static class MyUserHook implements UserHook, UserSessionsAware {
        UserSessions<?, ?> userSessions;

        @Override
        public void setUserSessions(UserSessions<?, ?> userSessions) {
            this.userSessions = userSessions;
        }

        @Override
        public void into(UserSession userSession) {
            long userId = userSession.getUserId();
            log.info("{}:{}  userId:{}, into"
                    , Bundle.getMessage(MessageKey.userHookInto)
                    , userSessions.countOnline()
                    , userId);
        }

        @Override
        public void quit(UserSession userSession) {

            long userId = userSession.getUserId();
            log.info("{}:{}  userId:{}, quit"
                    , Bundle.getMessage(MessageKey.userHookQuit)
                    , userSessions.countOnline()
                    , userId);
        }

    }

}
